//
// Created by hunan on 25-7-13.
//

#include "download_task.h"

#include "download_net_operate.h"
#include "public_module/http_protocol/http_protocol_msg.h"
#include "public_module/log_module/log_public_module.h"
#include "public_module/loop_module/loop.h"
#include "public_module/protocol_connection/http_protocol_operation.h"
#include "public_module/protocol_connection/protocol_connection.h"
#include "public_module/protocol_connection/protocol_errno.h"
#include "public_module/timer/timer_task.h"

DownloadTask::DownloadTask()
{
    m_net_operate = nullptr;
    m_loop = new Loop;
}

void DownloadTask::Download(const std::string & url, const std::string & filename, int number)
{
    m_url = url;
    m_filename = filename;
    m_net_operate = std::make_shared<DownloadNetOperate>(m_loop);
    m_net_operate->ResizeConnection(number);
    HeadFileDataSize();
    m_loop->Run();
}

void DownloadTask::HeadFileDataSize()
{
    auto connection = m_net_operate->GetConnection();
    int result = connection->InitServerUrl(m_url);
    if (result == EmProtocolErrno_SUCCESS)
    {
        connection->OnProtocolConnect([](auto conn, int result) {

            if (result == EmProtocolErrno_SUCCESS)
            {
                auto protocol = static_cast<HttpProtocolOperation *>(conn->GetProtocolOperation());
                auto msg = protocol->HttpRequestMsg();
                msg->AddHeaderLine("HEAD", conn->ProtocolData().m_uri, "HTTP/1.1");
                msg->AddHeaderSign("Host", conn->ProtocolData().m_host);
                std::string content;
                msg->String(content);
                conn->SendMessage((const uint8_t*)content.c_str(), content.size());
                //protocol->ClearMsg();
            }
            else
            {
                LOG_PUBLIC_MODULE("ProtocolConnection(%p) on connection callback result : %d", conn, result);
            }
        });
        connection->OnProtocolMessage([this](auto conn, ProtocolOperation * operation) {

            auto protocol = static_cast<HttpProtocolOperation *>(operation);
            auto msg = protocol->HttpResponseMsg();
            m_net_operate->ReleaseConnection(conn->shared_from_this());

            LOG_PUBLIC_MODULE("ProtocolConnection(%p) HEAD file data size http code:%s,body size:%s,ETag:%s",
                conn, msg->GetCode().c_str(), msg->GetHeaderValue("Content-Length").c_str(), msg->GetHeaderValue("ETag").c_str());
            if (msg->GetCode() == "200" && !msg->GetHeaderValue("Content-Length").empty())
            {
                m_etag = msg->GetHeaderValue("ETag");
                if (msg->GetCode() == "200")
                {
                    uint64_t content_length = atol(msg->GetHeaderValue("Content-Length").c_str());
                    protocol->ClearMsg();
                    CreateFileData(content_length);
                }
            }
            else
            {
                protocol->ClearMsg();
                CreateFileData(0);
            }
        });
    }
    else
    {
        LOG_PUBLIC_MODULE("DownloadTask(%p) HeadFileDataSize Get Url(%s) File Data Size", this, m_url.c_str());
    }
}

void DownloadTask::CreateFileData(uint64_t data_size)
{
    DownloadFileOperate::FileData data = {};
    data.m_file_name = m_filename;
    data.m_file_size = data_size;
    m_file_operate = std::make_shared<DownloadFileOperate>(data);
    int result = m_file_operate->InitDownloadFileOperate();

    LOG_PUBLIC_MODULE("DownloadTask(%p) CreateFileData init download file operate", this, result);
    if (!m_etag.empty() && !m_file_operate->GetLocalFileHash().empty() && m_etag.find(m_file_operate->GetLocalFileHash()) != std::string::npos)
    {
        LOG_PUBLIC_MODULE("DownloadTask(%p) CreateFileData finish", this);
        m_net_operate->CloseConnection();
        return;
    }
    ReadyForDownload();
    m_statistic_timer = std::make_shared<TimerTask>(m_loop);
    m_statistic_timer->ExpireFromNow(1000, [this]() {

        PrintStatistic();
    });
}

void DownloadTask::ReadyForDownload()
{
    for (int index = 0; index < 10; index++)
    {
        auto connection = m_net_operate->GetConnection();
        if (!connection)
        {
            break;
        }
        m_net_operate->ReleaseConnection(connection);
        DownloadFile();
    }
}

void DownloadTask::PrintStatistic() const
{
    if (m_file_operate)
    {
        m_file_operate->PrintStatistic();
    }
}
void DownloadTask::DownloadFile()
{
    auto connection = m_net_operate->GetConnection();
    auto function = [this, connection]() {

        // 0-(size-1) total size bytes
        int min = 0;
        int max = 0;
        if (m_file_operate->GetOneFragment(min, max))
        {
            auto protocol = static_cast<HttpProtocolOperation *>(connection->GetProtocolOperation());
            auto msg = protocol->HttpRequestMsg();
            msg->AddHeaderLine("GET", connection->ProtocolData().m_uri, "HTTP/1.1");
            msg->AddHeaderSign("Host", connection->ProtocolData().m_host);
            std::string bytes = "bytes=" + std::to_string(min) + "-" + std::to_string(max);

            msg->AddHeaderSign("Range", bytes);
            LOG_PUBLIC_MODULE("DownloadTask(%p) DownloadFile download min:%d,max:%d", this, min, max);
            std::string content;
            msg->String(content);
            connection->SendMessage((const uint8_t*)content.c_str(), content.size());
            connection->OnProtocolMessage([this, min, max](auto conn, ProtocolOperation * operation) {

                auto protocol = static_cast<HttpProtocolOperation *>(operation);
                auto msg = protocol->HttpResponseMsg();
                LOG_PUBLIC_MODULE("DownloadTask(%p) download min:%d,max:%d finish", this, min, max);
                m_file_operate->PushFileData(min, max, msg->Body().data(), msg->Body().size());
                protocol->ClearMsg();
                m_net_operate->ReleaseConnection(conn->shared_from_this());
                if (!m_file_operate->DownloadAll())
                {
                    DownloadFile();
                }
                else
                {
                    m_file_operate->WriteToFile();
                    std::string md5 = m_file_operate->GetLocalFileHash();
                    LOG_PUBLIC_MODULE("ProtocolConnection(%p) write to file done, file md5:%s and etag:%s", conn, md5.c_str(), m_etag.c_str());
                    m_net_operate->CloseConnection();
                    m_statistic_timer->Cancel();
                }
            });
            connection->OnProtocolDisConn([this, min, max](ProtocolConnection * conn, int) {

                m_net_operate->ReleaseConnection(conn->shared_from_this());
                if (min >= 0 && max > 0)
                {
                    this->m_file_operate->DownFileDataFail(min, max);
                }
                DownloadFile();
            });
        }
        else if (m_file_operate->GetRemoteFileSize() == 0)
        {
            auto protocol = static_cast<HttpProtocolOperation *>(connection->GetProtocolOperation());
            auto msg = protocol->HttpRequestMsg();
            msg->AddHeaderLine("GET", connection->ProtocolData().m_uri, "HTTP/1.1");
            msg->AddHeaderSign("Host", connection->ProtocolData().m_host);
            std::string content;
            msg->String(content);
            connection->SendMessage((const uint8_t*)content.c_str(), content.size());
            connection->OnProtocolMessage([this](auto conn, ProtocolOperation * operation) {

                auto protocol = static_cast<HttpProtocolOperation *>(operation);
                auto msg = protocol->HttpResponseMsg();
                m_file_operate->PushFileData(0, msg->Body().size(), msg->Body().data(), msg->Body().size());
                protocol->ClearMsg();
                m_net_operate->ReleaseConnection(conn->shared_from_this());
                if (!m_file_operate->DownloadAll())
                {
                    DownloadFile();
                }
                else
                {
                    m_file_operate->WriteToFile();
                    std::string md5 = m_file_operate->GetLocalFileHash();
                    LOG_PUBLIC_MODULE("ProtocolConnection(%p) write to file done, file md5:%s and etag:%s", conn, md5.c_str(), m_etag.c_str());
                    m_net_operate->CloseConnection();
                    m_statistic_timer->Cancel();
                }
            });
        }
        else
        {
            m_net_operate->ReleaseConnection(connection);
        }
    };
    int result = connection->InitServerUrl(m_url);
    if (result == EmProtocolErrno_CONNECTED)
    {
        function();
    }
    else
    {
        connection->OnProtocolConnect([function](auto conn, int result) {
            if (result == EmProtocolErrno_SUCCESS)
            {
                function();
            }
        });
    }
}




